#include "stdafx.h"
#include "../../xrEngine/igame_persistent.h"
#include "../../xrEngine/irenderable.h"
#include "../xrRender/FBasicVisual.h"

#include "r3_R_sun_support.h"

const	float	tweak_rain_COP_initial_offs			= 1200.f;
const	float	tweak_rain_ortho_xform_initial_offs	= 1000.f	;	//. ?

//	Defined in r2_R_sun.cpp
Fvector3		wform	(Fmatrix& m, Fvector3& v);

//////////////////////////////////////////////////////////////////////////
// tables to calculate view-frustum bounds in world space
// note: D3D uses [0..1] range for Z
static Fvector3		corners [8]			= {
	{ -1, -1,  0 },		{ -1, -1, +1},
	{ -1, +1, +1 },		{ -1, +1,  0},
	{ +1, +1, +1 },		{ +1, +1,  0},
	{ +1, -1, +1},		{ +1, -1,  0}
};
static int			facetable[6][4]		= {
	{ 0, 3, 5, 7 },		{ 1, 2, 3, 0 },
	{ 6, 7, 5, 4 },		{ 4, 2, 1, 6 },
	{ 3, 2, 4, 5 },		{ 1, 0, 7, 6 },
};

//////////////////////////////////////////////////////////////////////////
void CRender::render_rain()
{
	//return;
	float	fRainFactor	= g_pGamePersistent->Environment().CurrentEnv->rain_density;
	if ( fRainFactor < EPS_L )			return;

	PIX_EVENT(render_rain);

	D3DXMATRIX		m_LightViewProj;

	//	Use light as placeholder for rain data.
	light			RainLight;

	//static const float	source_offset		= 40.f;

	static const float	source_offset		= 10000.f;
	RainLight.direction.set(0.0f, -1.0f, 0.0f);
	RainLight.position.set(Device.vCameraPosition.x,Device.vCameraPosition.y+source_offset,Device.vCameraPosition.z);

	float	fBoundingSphereRadius = 0;

	// calculate view-frustum bounds in world space
	Fmatrix	ex_project, ex_full, ex_full_inverse;
	{
		//	
		const float fRainFar = ps_r3_dyn_wet_surf_far;
		ex_project.build_projection	(deg2rad(Device.fFOV/* *Device.fASPECT*/),Device.fASPECT,VIEWPORT_NEAR, fRainFar); 
		ex_full.mul					(ex_project,Device.mView);
		D3DXMatrixInverse			((D3DXMATRIX*)&ex_full_inverse,0,(D3DXMATRIX*)&ex_full);

		//	Calculate view frustum were we can see dynamic rain radius
		{
			//	b^2 = 2RH, B - side enge of the pyramid, h = height
			//	R = b^2/(2*H)
			const float H = fRainFar;
			const float a = tanf(deg2rad(Device.fFOV)/2);
			const float c = tanf(deg2rad(Device.fFOV*Device.fASPECT)/2);
			const float b_2 = H*H* ( 1.0f + a*a + c*c );
			fBoundingSphereRadius = b_2/(2.0f*H);
		}
	}

	//Device.vCameraDirection

	// Compute volume(s) - something like a frustum for infinite directional light
	// Also compute virtual light position and sector it is inside
	CFrustum					cull_frustum;
	xr_vector<Fplane>			cull_planes;
	Fvector3					cull_COP;
	CSector*					cull_sector;
	Fmatrix						cull_xform;
	{
		FPU::m64r					();
		// Lets begin from base frustum
		Fmatrix		fullxform_inv	= ex_full_inverse;
#ifdef	_DEBUG
		typedef		DumbConvexVolume<true>	t_volume;
#else
		typedef		DumbConvexVolume<false>	t_volume;
#endif
		t_volume					hull;
		{
			hull.points.reserve		(9);
			for	(int p=0; p<8; p++)	{
				Fvector3				xf	= wform		(fullxform_inv,corners[p]);
				hull.points.push_back	(xf);
			}
			for (int plane=0; plane<6; plane++)	{
				hull.polys.push_back(t_volume::_poly());
				for (int pt=0; pt<4; pt++)	
					hull.polys.back().points.push_back(facetable[plane][pt]);
			}
		}
		//hull.compute_caster_model	(cull_planes,fuckingsun->direction);
		hull.compute_caster_model	(cull_planes,RainLight.direction);
#ifdef	_DEBUG
		for (u32 it=0; it<cull_planes.size(); it++)
			RImplementation.Target->dbg_addplane(cull_planes[it],0xffffffff);
#endif

		// Search for default sector - assume "default" or "outdoor" sector is the largest one
		//. hack: need to know real outdoor sector
		CSector*	largest_sector		= 0;
		float		largest_sector_vol	= 0;
		for		(u32 s=0; s<Sectors.size(); s++)
		{
			CSector*			S		= (CSector*)Sectors[s]	;
			dxRender_Visual*		V		= S->root()				;
			float				vol		= V->vis.box.getvolume();
			if (vol>largest_sector_vol)	{
				largest_sector_vol		= vol;
				largest_sector			= S;
			}
		}
		cull_sector	= largest_sector;

		// COP - 100 km away
		cull_COP.mad				(Device.vCameraPosition, RainLight.direction, -tweak_rain_COP_initial_offs	);
		cull_COP.x += fBoundingSphereRadius*Device.vCameraDirection.x;
		cull_COP.z += fBoundingSphereRadius*Device.vCameraDirection.z;

		// Create frustum for query
		cull_frustum._clear			();
		for (u32 p=0; p<cull_planes.size(); p++)
			cull_frustum._add		(cull_planes[p]);
		

		// Create approximate ortho-xform
		// view: auto find 'up' and 'right' vectors
		Fmatrix						mdir_View, mdir_Project;
		Fvector						L_dir,L_up,L_right,L_pos;
		L_pos.set					(RainLight.position);
		L_dir.set					(RainLight.direction).normalize	();
		L_right.set					(1,0,0);					if (_abs(L_right.dotproduct(L_dir))>.99f)	L_right.set(0,0,1);
		L_up.crossproduct			(L_dir,L_right).normalize	();
		L_right.crossproduct		(L_up,L_dir).normalize		();
		mdir_View.build_camera_dir	(L_pos,L_dir,L_up);

		// projection: box
		//	Simple
		Fbox	frustum_bb;			frustum_bb.invalidate();
		for (int it=0; it<8; it++)	{
			//for (int it=0; it<9; it++)	{
			Fvector	xf	= wform		(mdir_View,hull.points[it]);
			frustum_bb.modify		(xf);
		}
		Fbox&	bb					= frustum_bb;
		bb.grow				(EPS);

		//	HACK
		//	TODO: DX10: Calculate bounding sphere for view frustum
		//	TODO: DX10: Reduce resolution.
		//bb.min.x = -50;
		//bb.max.x = 50;
		//bb.min.y = -50;
		//bb.max.y = 50;

		//	Offset RainLight position to center rain shadowmap
		Fvector3	vRectOffset;
		vRectOffset.set( fBoundingSphereRadius*Device.vCameraDirection.x, 0, fBoundingSphereRadius*Device.vCameraDirection.z);
		bb.min.x = -fBoundingSphereRadius + vRectOffset.x;
		bb.max.x = fBoundingSphereRadius + vRectOffset.x;
		bb.min.y = -fBoundingSphereRadius + vRectOffset.z;
		bb.max.y = fBoundingSphereRadius + vRectOffset.z;

		//D3DXMatrixOrthoOffCenterLH	((D3DXMATRIX*)&mdir_Project,bb.min.x,bb.max.x,  bb.min.y,bb.max.y,  bb.min.z-tweak_rain_ortho_xform_initial_offs,bb.max.z);
		D3DXMatrixOrthoOffCenterLH	((D3DXMATRIX*)&mdir_Project,bb.min.x,bb.max.x,  bb.min.y,bb.max.y,  bb.min.z-tweak_rain_ortho_xform_initial_offs,bb.min.z+2*tweak_rain_ortho_xform_initial_offs);

		cull_xform.mul		(mdir_Project,mdir_View	);

		s32		limit				= _min(RImplementation.o.smapsize, ps_r3_dyn_wet_surf_sm_res);

		// build viewport xform
		float	view_dim			= float(limit);
		float	fTexelOffs			= (.5f / RImplementation.o.smapsize);
		Fmatrix	m_viewport			= {
			view_dim/2.f,	0.0f,				0.0f,		0.0f,
			0.0f,			-view_dim/2.f,		0.0f,		0.0f,
			0.0f,			0.0f,				1.0f,		0.0f,
			view_dim/2.f+fTexelOffs,	view_dim/2.f+fTexelOffs,		0.0f,		1.0f
		};
		Fmatrix				m_viewport_inv;
		D3DXMatrixInverse	((D3DXMATRIX*)&m_viewport_inv,0,(D3DXMATRIX*)&m_viewport);

		// snap view-position to pixel
		//	snap zero point to pixel
		Fvector cam_proj	= wform		(cull_xform,Fvector().set(0,0,0)	);
		Fvector	cam_pixel	= wform		(m_viewport,cam_proj				);
		cam_pixel.x			= floorf	(cam_pixel.x);
		cam_pixel.y			= floorf	(cam_pixel.y);
		Fvector cam_snapped	= wform		(m_viewport_inv,cam_pixel);
		Fvector diff;		diff.sub	(cam_snapped,cam_proj				);
		Fmatrix adjust;		adjust.translate(diff);
		cull_xform.mulA_44	(adjust);

		RainLight.X.D.minX			= 0;
		RainLight.X.D.maxX			= limit;
		RainLight.X.D.minY			= 0;
		RainLight.X.D.maxY			= limit;

		// full-xform
		FPU::m24r			();
	}

	// Begin SMAP-render
	{
		bool	bSpecialFull					= mapNormalPasses[1][0].size() || mapMatrixPasses[1][0].size() || mapSorted.size();
		VERIFY									(!bSpecialFull);
		HOM.Disable								();
		phase									= PHASE_SMAP;
		r_pmask	(true,false	);
	}

	// Fill the database
	//r_dsgraph_render_subspace				(cull_sector, &cull_frustum, cull_xform, cull_COP, TRUE);
	r_dsgraph_render_subspace				(cull_sector, &cull_frustum, cull_xform, cull_COP, FALSE);

	// Finalize & Cleanup
	RainLight.X.D.combine					= cull_xform;	//*((Fmatrix*)&m_LightViewProj);

	// Render shadow-map
	//. !!! We should clip based on shrinked frustum (again)
	{
		bool	bNormal							= mapNormalPasses[0][0].size() || mapMatrixPasses[0][0].size();
		bool	bSpecial						= mapNormalPasses[1][0].size() || mapMatrixPasses[1][0].size() || mapSorted.size();
		if ( bNormal || bSpecial)	{
			Target->phase_smap_direct			(&RainLight	, SE_SUN_RAIN_SMAP	);
			RCache.set_xform_world				(Fidentity					);
			RCache.set_xform_view				(Fidentity					);
			RCache.set_xform_project			(RainLight.X.D.combine	);	
			r_dsgraph_render_graph				(0)	;
			//if (ps_r2_ls_flags.test(R2FLAG_SUN_DETAILS))	
			//	Details->Render					()	;
		}
	}

	// End SMAP-render
	{
		//		fuckingsun->svis.end					();
		r_pmask									(true,false);
	}

	// Restore XForms
	RCache.set_xform_world		(Fidentity			);
	RCache.set_xform_view		(Device.mView		);
	RCache.set_xform_project	(Device.mProject	);

	// Accumulate
	Target->phase_rain	();
	Target->draw_rain	(RainLight);
}